package io.flutter.embedding.engine.systemchannels;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import java.util.HashMap;

import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.view.AccessibilityBridge;

/**
 * System channel that sends accessibility requests and events from Flutter to Android.
 * <p>
 * See {@link AccessibilityMessageHandler}, which lists all accessibility requests and
 * events that might be sent from Flutter to the Android platform.
 */
public class AccessibilityChannel {
  private static final String TAG = "AccessibilityChannel";

  @NonNull
  public final BasicMessageChannel<Object> channel;
  @NonNull
  public final FlutterJNI flutterJNI;
  @Nullable
  private AccessibilityMessageHandler handler;

  private final BasicMessageChannel.MessageHandler<Object> parsingMessageHandler = new BasicMessageChannel.MessageHandler<Object>() {
    @Override
    public void onMessage(@Nullable Object message, @NonNull BasicMessageChannel.Reply<Object> reply) {
      // If there is no handler to respond to this message then we don't need to
      // parse it. Return.
      if (handler == null) {
        return;
      }

      @SuppressWarnings("unchecked")
      final HashMap<String, Object> annotatedEvent = (HashMap<String, Object>) message;
      final String type = (String) annotatedEvent.get("type");
      @SuppressWarnings("unchecked")
      final HashMap<String, Object> data = (HashMap<String, Object>) annotatedEvent.get("data");

      Log.v(TAG, "Received " + type + " message.");
      switch (type) {
        case "announce":
          String announceMessage = (String) data.get("message");
          if (announceMessage != null) {
            handler.announce(announceMessage);
          }
          break;
        case "tap": {
          Integer nodeId = (Integer) annotatedEvent.get("nodeId");
          if (nodeId != null) {
            handler.onTap(nodeId);
          }
          break;
        }
        case "longPress": {
          Integer nodeId = (Integer) annotatedEvent.get("nodeId");
          if (nodeId != null) {
            handler.onLongPress(nodeId);
          }
          break;
        }
        case "tooltip": {
          String tooltipMessage = (String) data.get("message");
          if (tooltipMessage != null) {
            handler.onTooltip(tooltipMessage);
          }
          break;
        }
      }
    }
  };

  /**
   * Constructs an {@code AccessibilityChannel} that connects Android to the Dart code
   * running in {@code dartExecutor}.
   *
   * The given {@code dartExecutor} is permitted to be idle or executing code.
   *
   * See {@link DartExecutor}.
   */
  public AccessibilityChannel(@NonNull DartExecutor dartExecutor, @NonNull FlutterJNI flutterJNI) {
    channel = new BasicMessageChannel<>(dartExecutor, "flutter/accessibility", StandardMessageCodec.INSTANCE);
    channel.setMessageHandler(parsingMessageHandler);
    this.flutterJNI = flutterJNI;
  }

  /**
   * Informs Flutter that the Android OS currently has accessibility enabled.
   *
   * To accommodate enabled accessibility, this method instructs Flutter to activate
   * its semantics tree, which forms the basis of Flutter's accessibility support.
   */
  public void onAndroidAccessibilityEnabled() {
    flutterJNI.setSemanticsEnabled(true);
  }

  /**
   * Informs Flutter that the Android OS currently has accessibility disabled.
   *
   * Given that accessibility is not required at this time, this method instructs Flutter
   * to deactivate its semantics tree.
   */
  public void onAndroidAccessibilityDisabled() {
    flutterJNI.setSemanticsEnabled(false);
  }

  /**
   * Instructs Flutter to activate/deactivate accessibility features corresponding to the
   * flags provided by {@code accessibilityFeatureFlags}.
   */
  public void setAccessibilityFeatures(int accessibilityFeatureFlags) {
    flutterJNI.setAccessibilityFeatures(accessibilityFeatureFlags);
  }

  /**
   * Instructs Flutter to perform the given {@code action} on the {@code SemanticsNode}
   * referenced by the given {@code virtualViewId}.
   *
   * One might wonder why Flutter would need to be instructed that the user wants to perform
   * an action. When the user is touching the screen in accessibility mode, Android takes over the
   * touch input, categorizing input as one of a many accessibility gestures. Therefore, Flutter
   * does not have an opportunity to react to said touch input. Instead, Flutter must be notified
   * by Android of the desired action. Additionally, some accessibility systems use other input
   * methods, such as speech, to take virtual actions. Android interprets those requests and then
   * instructs the app to take the appropriate action.
   */
  public void dispatchSemanticsAction(int virtualViewId, @NonNull AccessibilityBridge.Action action) {
    flutterJNI.dispatchSemanticsAction(virtualViewId, action);
  }

  /**
   * Instructs Flutter to perform the given {@code action} on the {@code SemanticsNode}
   * referenced by the given {@code virtualViewId}, passing the given {@code args}.
   */
  public void dispatchSemanticsAction(int virtualViewId, @NonNull AccessibilityBridge.Action action, @Nullable Object args) {
    flutterJNI.dispatchSemanticsAction(virtualViewId, action, args);
  }

  /**
   * Sets the {@link AccessibilityMessageHandler} which receives all events and requests
   * that are parsed from the underlying accessibility channel.
   */
  public void setAccessibilityMessageHandler(@Nullable AccessibilityMessageHandler handler) {
    this.handler = handler;
    flutterJNI.setAccessibilityDelegate(handler);
  }

  /**
   * Handler that receives accessibility messages sent from Flutter to Android
   * through a given {@link AccessibilityChannel}.
   *
   * To register an {@code AccessibilityMessageHandler} with a {@link AccessibilityChannel},
   * see {@link AccessibilityChannel#setAccessibilityMessageHandler(AccessibilityMessageHandler)}.
   */
  public interface AccessibilityMessageHandler extends FlutterJNI.AccessibilityDelegate {
    /**
     * The Dart application would like the given {@code message} to be announced.
     */
    void announce(@NonNull String message);

    /**
     * The user has tapped on the widget with the given {@code nodeId}.
     */
    void onTap(int nodeId);

    /**
     * The user has long pressed on the widget with the given {@code nodeId}.
     */
    void onLongPress(int nodeId);

    /**
     * The user has opened a tooltip.
     */
    void onTooltip(@NonNull String message);
  }
}
